from fastapi import HTTPException

from datetime import datetime

from src.database.models import (
    Weather_condition,
    Weather,
    Weather_to_forecast,
    Forecast,
    Region
)
from src.region_service.schemas import Region_schema
from src.weather_service.schemas_weather import (
    Upload_weather,
    Update_weather,
    Weather_record,
    Weather_response,
    Weather_response_with_region_name,
    Weather_to_forecast_schema,
    Weather_to_sql
)

from src.database.my_crud import My_crud

from src.region_service.services.region import Region_service
from src.user_service.service.auth import raise_401_error

weather_crud = My_crud(Weather)
weather_condition_crud = My_crud(Weather_condition)
forecast_crud = My_crud(Forecast)
weather_to_forecast_crud = My_crud(Weather_to_forecast)
region_crud = My_crud(Region)


class Weather_service:
    async def __create_relation(self, weatherId: int, weatherForecast: int):
        weather_to_forecast_crud.create(
            record=Weather_to_forecast_schema(
                weatherId=weatherId,
                weatherForecast=weatherForecast
            )
        )

    async def __raise_condition(self, condition: str):
        if condition is None:
            return None

        if not await weather_condition_crud.exist(
            [Weather_condition.condition == condition]
        ):
            raise HTTPException(status_code=400, detail="bad condition")

    async def __region_not_found(self, regionId):
        if not await region_crud.exist([Region.id == regionId]):
            raise HTTPException(status_code=404, detail="bad regionId")

    async def __forecast_not_found(self, weatherForecast):
        if not await forecast_crud.exist([Forecast.id == weatherForecast]):
            raise HTTPException(
                status_code=404,
                detail="bad weatherForecast"
            )

    # useful funcs end
    async def create_weather(
        self,
        payload: Upload_weather,
        user_id: int
    ):
        await raise_401_error(user_id)

        await self.__raise_condition(payload.weatherCondition)
        await self.__region_not_found(regionId=payload.regionId)
        for weatherForecast in payload.weatherForecast:
            await self.__forecast_not_found(weatherForecast=weatherForecast)

        new_payload = Weather_to_sql(**payload.model_dump())
        new_payload.measurementDateTime = datetime.fromisoformat(
                payload.measurementDateTime
            ).replace(tzinfo=None)

        weather_record = await weather_crud.create(
            record=new_payload,
            Output_model=Weather_record
        )

        for weatherForecast in payload.weatherForecast:
            await self.__create_relation(weather_record.id, weatherForecast)

        resp = Weather_response(id=weather_record.id, **payload.model_dump())
        return resp

    async def get_all_weather(self, regionId: int, user_id: int):
        await raise_401_error(user_id)

        await self.__region_not_found(regionId=regionId)

        region_record: Region_schema = (await region_crud.get(
            filters=[Region.id == regionId],
            Output_model=Region_schema
        ))

        regionName = region_record.name

        weather_records = await weather_crud.get(
            filters=[Weather.regionId == regionId],
            Output_model=Weather_record,
            multi=True
        )

        result = []
        for weather_record in weather_records:
            relations = await weather_to_forecast_crud.get(
                filters=[Weather_to_forecast.weatherId == weather_record.id],
                Output_model=Weather_to_forecast_schema,
                multi=True
            )

            weatherForecasts = [
                relation.weatherForecast for relation in relations
            ]

            record = Weather_response_with_region_name(
                regionName=regionName,
                weatherForecast=weatherForecasts,
                **weather_record.model_dump()
            )
            record_dict = record.model_dump()

            record_dict["osmLink"] = f"0.0.0.0:8080/map?latitude={region_record.latitude_1}&longitude={region_record.longitude_1}&zoom=12"
            result.append(record_dict)
        return result

    async def edit_weather(
        self,
        regionId: int,
        payload: Update_weather,
        user_id: int
    ):
        await raise_401_error(user_id)

        if not await weather_crud.exist([Weather.regionId == regionId]):
            raise HTTPException(
                status_code=404,
                detail="not records by regionId"
            )

        weather_record = (await weather_crud.get(
            filters=[Weather.regionId == regionId],
            Output_model=Weather_record,
            # order_by=Weather.measurementDateTime.desc(),
            offset=0,
            limit=1,
            multi=True
        ))[0]

        weatherIdToUpdate = weather_record.id

        copy_payload = payload.model_dump(exclude_none=True)
        if payload.measurementDateTime is str:
            copy_payload["measurementDateTime"] = datetime.fromisoformat(
                    payload.measurementDateTime
                ).replace(tzinfo=None)
        else:
            copy_payload["measurementDateTime"] = (
                payload.measurementDateTime.replace(tzinfo=None)
            )

        copy_payload.pop("regionName")
        copy_payload.pop("weatherForecast")

        await weather_crud.patch(
            filters=[Weather.id == weatherIdToUpdate],
            new_data=copy_payload
        )

        result = payload.model_dump()
        result["id"] = weatherIdToUpdate
        await Region_service().edit_region_name(
            regionId,
            payload.regionName
        )
        return result

    async def delete_all_weather(self, regionId: int, user_id: int):
        await raise_401_error(user_id)

        self.__region_not_found(regionId=regionId)

        weather_records = await weather_crud.get(
            filters=[Weather.regionId == regionId],
            Output_model=Weather_record,
            multi=True
        )

        for weather_record in weather_records:
            relation_records = await weather_to_forecast_crud.get(
                filters=[Weather_to_forecast.weatherId == weather_record.id],
                Output_model=Weather_to_forecast_schema,
                multi=True
            )
            for relation_record in relation_records:
                await forecast_crud.remove(
                    [Forecast.id == relation_record.weatherForecast]
                )

            await weather_to_forecast_crud.remove(
                [Weather_to_forecast.weatherForecast == weather_record.id]
            )

        await weather_crud.remove(
            [Weather.regionId == regionId]
        )
        return None
